package com.zyl.blog.common.utils;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.PutObjectRequest;
import com.zyl.blog.common.config.AliOSSProperties;
import com.zyl.blog.common.utils.Extreme.LogUtil;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

/**
 * 阿里云 OSS 文件上传工具类（线程安全，单例复用OSS客户端）
 *
 * <p>功能说明：
 * 1. 自动生成带日期目录的唯一文件名，避免文件覆盖
 * 2. 支持生成临时访问URL（需配置 urlExpireSeconds）
 * 3. 内置客户端连接池，避免频繁创建/销毁
 * 4. 统一异常处理，避免敏感信息泄露
 * @version v1.0
 * @ProjectName: Personal_Blog
 * @Package: com.zyl.common.utils
 * @ClassName: AliOssUtil
 * @Author FinalFantasy
 * @Date 2025/2/14-16:21
 */
@Component
public class AliOSSUtils {

    private final AliOSSProperties properties;
    private OSS ossClient;
    private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");

    private final List<String> pathWhitelist = new ArrayList<>(){{
        add("static/images");
        add("static/previews");  // 新增预览路径
        add("static/docs");
        add("static/video");
        add("temp");
        add("user/avatar");
    }};

    /**
     * 初始化OSS客户端（Spring容器管理生命周期）
     */
    public AliOSSUtils(AliOSSProperties properties) {
        this.properties = properties;
    }

    @PostConstruct
    public void init() {
        this.ossClient = new OSSClientBuilder()
                .build(
                        properties.getEndpoint(),
                        properties.getAccessKeyId(),
                        properties.getAccessKeySecret()
                );
        LogUtil.info("OSS客户端初始化成功，Endpoint: {}", properties.getEndpoint());
    }

    /**
     * 通用文件上传方法
     * @param file 上传文件
     * @param basePath OSS存储基础路径（需在白名单中）
     * @return 签名访问URL
     * @throws IOException 文件读取异常
     * @throws IllegalArgumentException 非法路径或文件名
     */
    public String upload(MultipartFile file, String basePath) throws IOException {
        // 1. 参数校验
        validateFile(file);
        validatePath(basePath);

        // 2. 生成对象名称
        String originalFilename = file.getOriginalFilename();
        String fileExtension = null;
        if (originalFilename != null) {
            fileExtension = extractFileExtension(originalFilename);
        }
        String objectName = generateObjectName(basePath, fileExtension);

        // 3. 执行上传
        try (InputStream inputStream = file.getInputStream()) {
            PutObjectRequest request = new PutObjectRequest(
                    properties.getBucketName(),
                    objectName,
                    inputStream
            );
            ossClient.putObject(request);
            LogUtil.info("文件上传成功，Bucket: {}，Obj: {}",
                    properties.getBucketName(), objectName);
        }

        // 4. 生成访问URL（公共读权限）
        return generatePublicUrl(objectName);
    }

    public String upload(byte[] data, String basePath, String fileExtension) {
        validatePath(basePath);
        String objectName = generateObjectName(basePath, fileExtension);
        ossClient.putObject(
                properties.getBucketName(),
                objectName,
                new ByteArrayInputStream(data)
        );
        LogUtil.info("文件上传成功，Bucket: {}，Obj: {}",
                properties.getBucketName(), objectName);
        return generatePublicUrl(objectName);
    }

    /**
     * 默认路径上传（static/images）
     */
    public String upload(MultipartFile file) throws IOException {
        return upload(file, "static/images");
    }

    @PreDestroy
    public void shutdown() {
        if (ossClient != null) {
            ossClient.shutdown();
            LogUtil.info("OSS客户端已关闭");
        }
    }

    // 校验文件有效性
    private void validateFile(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new IllegalArgumentException("上传文件不能为空");
        }

        String filename = file.getOriginalFilename();
        if (filename == null || filename.trim().isEmpty()) {
            throw new IllegalArgumentException("无效的文件名");
        }
    }

    // 校验存储路径合法性
    private void validatePath(String basePath) {
        if (!pathWhitelist.contains(basePath)) {
            throw new IllegalArgumentException("非法存储路径: " + basePath);
        }
    }

    // 生成带路径的对象名称
    private String generateObjectName(String basePath, String fileExtension) {
        String normalizedPath = basePath.replaceAll("/+$", "");
        String dateDir = LocalDate.now().format(dateFormatter);
        String uuid = UUID.randomUUID().toString().replace("-", "");

        return String.format("%s/%s/%s%s",
                normalizedPath,
                dateDir,
                uuid,
                fileExtension);
    }

    // 提取文件扩展名
    private String extractFileExtension(String filename) {
        int lastDotIndex = filename.lastIndexOf(".");
        if (lastDotIndex == -1) return "";

        String extension = filename.substring(lastDotIndex);
        // 防止包含非法字符
        return extension.replaceAll("[^a-zA-Z0-9.]", "");
    }

    // 生成签名URL
    private String generateSignedUrl(String objectName) {
        if (properties.getUrlExpireSeconds() <= 0) {
            throw new IllegalStateException("私有Bucket必须设置urlExpireSeconds");
        }

        Date expiration = new Date(System.currentTimeMillis() +
                properties.getUrlExpireSeconds() * 1000L);
        URL url = ossClient.generatePresignedUrl(
                properties.getBucketName(),
                objectName,
                expiration);

        // 移除不必要查询参数
        return url.toString().replaceFirst("&SecurityToken=[^&]*", "");
    }

    // 公共读
    private String generatePublicUrl(String objectName) {
        return String.format("https://%s.%s/%s",
                properties.getBucketName(),
                properties.getEndpoint(),
                objectName);
    }
}